package evaluator

import (
	"monkey/ast"
	"monkey/object"
)

//DefineMacros 在 AST 中查找宏定义并将其从 AST 中删除
func DefineMacros(program *ast.Program,env *object.Environment){
   definitions := []int{}
   for i,statement := range program.Statements{
		if isMacroDefinition(statement){
			addMacro(statement,env)
			definitions = append(definitions, i)
		}
   }
   for i := len(definitions) - 1; i >= 0; i-- {
		definitionIndex := definitions[i]
		program.Statements = append(program.Statements[:definitionIndex],program.Statements[definitionIndex+1:]... )
   }
}
//addMacro 所做的是向传入的*object.Environment 添加新构建的*object.Macro，并
//将其绑定到*ast.LetStatement 中给出的名称。
func addMacro(stmt ast.Statement,env *object.Environment){
	letStatement,_ := stmt.(*ast.LetStatement)
	macroLiteral,_ := letStatement.Value.(*ast.MacroLiteral)

	macro := &object.Macro{
		Parameters: macroLiteral.Parameters,
		Env:env,
		Body: macroLiteral.Body,
	}
	env.Set(letStatement.Name.Value,macro)
}
func isMacroDefinition(node ast.Statement) bool{
	letStatement,ok := node.(*ast.LetStatement)
	if !ok{
		return false
	}

	_,ok = letStatement.Value.(*ast.MacroLiteral)
	if !ok{
		return false
	}
	return true
}
//TODO:值得注意的是，Monkey 只允许在顶层定义宏，不会在遍历 Statements 时继续检
//查其中的子节点。这还是因为本书篇幅有限。Monkey 中宏的工作方式并没有这种固
//有限制。事实上情况恰恰相反，实现能够嵌套的宏定义可能是一个不错的练习，你觉得呢？

//ExpandMacros使用可信赖的辅助函数ast.Modify以递归的方式遍历表示program的 AST，查找宏调用
//如果当前节点是一个涉及宏调用的表达式，那么下一步就是对这个调用求值
func ExpandMacros(program ast.Node,env *object.Environment)ast.Node{
	return ast.Modify(program,func(node ast.Node)ast.Node{
		callExpression,ok := node.(*ast.CallExpression)
		if !ok{
			return node
		}
		macro,ok := isMacroCall(callExpression,env)
		if !ok{
			return node
		}
		args := quoteArgs(callExpression)
		evalEnv := extendMacroEnv(macro,args)

		evaluated := Eval(macro.Body,evalEnv)
		quote,ok := evaluated.(*object.Quote)
		if !ok{
			panic("we only support returning AST-node from macros")
		}
		return quote.Node
})
}

func isMacroCall(exp *ast.CallExpression,env *object.Environment)(*object.Macro,bool){
	identifier,ok := exp.Function.(*ast.Identifier)
	if !ok{
		return nil,false
	}
	obj,ok := env.Get(identifier.Value)
	if !ok{
		return nil,false
	}
	macro,ok := obj.(*object.Macro)
	if !ok{
		return nil,false
	}
	return macro,true
}
//quoteArgs ExpandMacros接受了参数并在quoteArgs的帮助下将它们转换为*object.Quote
func quoteArgs(exp *ast.CallExpression)[]*object.Quote{
	args := []*object.Quote{}
	for _,a := range exp.Arguments{
		args = append(args, &object.Quote{Node:a})
	}
	return args
}
//extendMacroEnv来扩展宏的环境，并将调用的参数绑定到宏字面量的参数名称中
func extendMacroEnv(macro *object.Macro,args []*object.Quote)*object.Environment{
	extended := object.NewEnclosedEnvironment(macro.Env)
	for paramIdex,param := range macro.Parameters{
		extended.Set(param.Value,args[paramIdex])
	}
	return extended
}